package main

import (
	"time"
	"global/libs/ws"
	"encoding/json"
	"global/model"
	"fmt"
	"global/constants"
	"github.com/garyburd/redigo/redis"
	"strings"
	"sync"
	"global/core"
	"global/util"
	"github.com/ngaut/log"
)

type CoinLike struct {
	req 				*ws.CoinLikeRequest
	handler 			*Handler
	ticker 				*time.Ticker

	length 				int
	userLikeCoins 		[]string
	sortedCoins 		[]string

	exit 				chan interface{}
	mux 				sync.Mutex
}

func (h *CoinLike) Init() {
	h.req = nil
	h.exit  = make(chan interface{})
	h.userLikeCoins = make([]string, 0)
	h.sortedCoins = make([]string, 0)
	h.ticker = time.NewTicker(2 * time.Second)
	h.setTimeTicker()
}

func (h *CoinLike) Clear()  {
	h.exit <- true
}

func (h *CoinLike) ReadWriteMessage(handler *Handler, message ws.RequestMessage) error {
	h.handler = handler

	var r ws.CoinLikeRequest
	msg := []byte(message.Message)
	err := json.Unmarshal(msg, &r)
	if err != nil {
		return err
	}

	// 检查参数
	if (r.Sort != "mv" && r.Sort != "price" && r.Sort != "percent") ||
		(r.Direction != "desc" && r.Direction != "asc") || r.Uid <= 0 {
		h.handler.response <- ws.ErrorMsg(ws.ErrParameter, constants.RequestCoinLike)
		return nil
	}
	h.req = &r
	err = h.getAndCacheCoinUserLiked(r.Uid)
	if err != nil {
		log.Errorf("Error: %v", err)
		return 	nil
	}
	if err = h.getAllSortedCoins(); err != nil {
		log.Errorf("Error: %s", err)
	}
	return h.pushData()
}

func (h *CoinLike) setTimeTicker() {
	go func() {
		for {
			select {
			case <-h.exit:
				h.ticker.Stop()
				return
			case <-h.ticker.C:
				h.pushData()
			}
		}
	}()
}

// 获取用户的关注的币种并缓存下来
func (h *CoinLike) getAndCacheCoinUserLiked(uid int) error {

	// 检查 key 存在不
	exists, err := h.checkCacheExists(uid)
	if err != nil {
		return err
	}

	coins := make([]string, 0)
	if !exists {
		likes, err := model.CoinLikeModel.SearchUserLikedCoins(uid)
		if err != nil {
			return err
		}
		for _, v := range likes {
			coins = append(coins, strings.ToLower(v.CoinSymbol))
		}

		if len(coins) == 0 {
			return nil
		}

		h.length = len(likes)

		// chache
		newCoins := h.cacheUserLikedCoins(uid, likes)
		if len(newCoins) < h.req.Page * constants.PageSize  {
			coins = newCoins
		} else {
			h.userLikeCoins = newCoins[:h.req.Page * constants.PageSize]
		}
	} else {
		// search form cache
		coins, err = h.getUserLikeCoinsFromCache(uid)
		if err != nil { return err }
	}

	h.mux.Lock()
	h.userLikeCoins = coins
	h.mux.Unlock()

	return nil
}

func (h *CoinLike) cacheUserLikedCoins(uid int, coins []*model.CoinLike) []string {
	key := fmt.Sprintf(constants.UserCoinLikeCache, uid)
	conn := h.handler.ctx.GetBaseRedis().Get()
	defer conn.Close()

	coinStrs := make([]string, 0)
	conn.Send("MULTI")
	for _, v := range coins {
		conn.Send("RPUSH", key, strings.ToLower(v.CoinSymbol))
		coinStrs = append(coinStrs, strings.ToLower(v.CoinSymbol))
	}
	conn.Do("EXEC")
	return coinStrs
}

// 检查缓存是否存在
func (h *CoinLike) checkCacheExists(uid int) (bool, error) {
	key := fmt.Sprintf(constants.UserCoinLikeCache, uid)
	conn := h.handler.ctx.GetBaseRedis().Get()
	defer conn.Close()
	return redis.Bool(conn.Do("EXISTS", key))
}

// 从缓存中获取
func (h *CoinLike) getUserLikeCoinsFromCache(uid int) ([]string, error) {
	var err error
	key := fmt.Sprintf(constants.UserCoinLikeCache, uid)
	conn := h.handler.ctx.GetBaseRedis().Get()
	defer conn.Close()

	h.length, err = redis.Int(conn.Do("LLEN", key))
	if err != nil {
		return nil, err
	}

	limit := h.req.Page * constants.PageSize - 1
	return redis.Strings(conn.Do("LRANGE", key, 0, limit))
}


func (h *CoinLike) getAllSortedCoins() error {
	conn := h.handler.ctx.GetBaseRedis().Get()
	defer conn.Close()
	coins, err := core.GetAllSortedCoins(conn, h.req.Sort, h.req.Direction)
	if err != nil {
		return err
	}

	sortedCoins := make([]string, 0)
	for _, v := range coins {
		for i := 0; i < len(h.userLikeCoins); i++ {
			if h.userLikeCoins[i] == v {
				sortedCoins = append(sortedCoins, v)
			}
		}
	}

	h.mux.Lock()
	h.sortedCoins = sortedCoins
	h.mux.Unlock()

	return nil
}

func (h *CoinLike) pushData() error {
	if h.req == nil {
		return nil
	}

	if len(h.sortedCoins) == 0 {
		h.response([]ws.CoinItem{})
		return nil
	}

	start := time.Now()
	conn := h.handler.ctx.GetBaseRedis().Get()
	defer conn.Close()

	rate, err := core.GetCnyRateByCoin(conn, "usd")
	if err != nil {
		return err
	}

	datas := make([]ws.CoinItem, 0)
	for _, coin := range h.sortedCoins {
		ticker, meta, err := core.GetCoinMetaAndTicker(conn, coin)
		if err != nil {
			return err
		}

		data := ws.CoinItem{
			Mid: 			meta.Mid,
			Symbol: 		strings.ToUpper(meta.Symbol),
			Logo: 			util.ImageUrl(h.handler.ctx.GetImgUrl(), meta.Logo),
			NameEn: 		meta.NameEn,
			NameCn: 		meta.NameCn,
			MarketValueUSD:	ticker.MarketValue,
			MarketValueCNY: util.GetUsdToCny(ticker.MarketValue, rate),
			Percent: 		ticker.Percent,
			PriceUSD: 		ticker.Close,
			PriceCNY: 		util.GetUsdToCny(ticker.Close, rate),
			UsdCny: 		rate,
		}

		datas = append(datas, data)
	}

	h.response(datas)

	log.Infof("get response data success: count: %d, take time: %s", len(datas), time.Since(start))
	return nil
}

func (h *CoinLike) response(datas []ws.CoinItem)  {
	var response ws.CoinListResponse
	response.Data = datas
	response.Total = h.length
	response.Page = h.req.Page
	response.PageSize = constants.PageSize
	h.handler.response <- ws.WsResponse(response, constants.RequestCoinLike)
}